﻿using System;
using System.Collections;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using Complaint.Common;
using Complaint.Helpers;
using Complaint.Models;
using Complaint.Models.Dtos;
using Complaint.Models.Dtos.Enums;

namespace Complaint.Controllers
{
    //
    // ViewModel Classes

    public class AccountFormViewModel : ViewModelWithDataList<User, PasswordQuestion, PasswordQuestionModel>
    {

        // Properties
        public SelectList Titles { get; private set; }

        // Constructor
        public AccountFormViewModel(User user, string textField, string valueField)
            : base(user, textField, valueField)
        {
            var titles = ConfigHelper
                .GetConfigValue("Titles")
                .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            var list = new SortedList();
            foreach (var s in titles)
            {
                list.Add(s, s);
            }

            Titles = new SelectList(list, "Key", "Value", "Mr");
        }
    }

    [HandleError]
    public class AccountController : Controller
    {

        // This constructor is used by the MVC framework to instantiate the controller using
        // the default forms authentication and membership providers.

        public AccountController()
            : this(null, null)
        {
        }

        // This constructor is not used by the MVC framework but is instead provided for ease
        // of unit testing this type. See the comments at the end of this file for more
        // information.
        public AccountController(IFormsAuthentication formsAuth, IMembershipService service)
        {
            FormsAuth = formsAuth ?? new FormsAuthenticationService();
            MembershipService = service ?? new AccountMembershipService();
        }

        public IFormsAuthentication FormsAuth
        {
            get;
            private set;
        }

        public IMembershipService MembershipService
        {
            get;
            private set;
        }

        public ActionResult Index()
        {

            return RedirectToAction("LogOn");
        }

        public ActionResult LogOn()
        {

            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
            Justification = "Needs to take same parameter type as Controller.Redirect()")]
        public ActionResult LogOn(string userName, string password, bool rememberMe, string returnUrl)
        {

            if (!ValidateLogOn(userName, password))
            {
                return View();
            }

            FormsAuth.SignIn(userName, rememberMe);
            if (!String.IsNullOrEmpty(returnUrl))
            {
                return Redirect(returnUrl);
            }
            return RedirectToAction("Index", "Home");
        }

        public ActionResult LogOff()
        {

            FormsAuth.SignOut();

            return RedirectToAction("Index", "Home");
        }

        public ActionResult Register()
        {
            ViewData["PasswordLength"] = Models.Dtos.User.MinRequiredPasswordLength;
            var user = new User();

            return View(
                new AccountFormViewModel(
                    user, "PasswordQuestionID", "Question"
                    ));
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Register(
            User user,
            string confirmPassword,
            int passwordQuestionId
            )
        {

            ViewData["PasswordLength"] = Models.Dtos.User.MinRequiredPasswordLength;

            // Attempt to register the user
            try
            {
                ValidateRegistration(user, confirmPassword);

                MembershipService.CreateUser(
                    user,
                    passwordQuestionId
                    );

                // user successfully created
                // FormsAuth.SignIn(user.Email, false /* createPersistentCookie */);
                // ViewData["ReturnUrl"] = Request.QueryString["ReturnUrl"];
                return View("Confirmation");
            }
            catch (ComplaintDataException e)
            {
                foreach (var error in e.DataValidationErrors)
                {
                    ModelState.AddModelError(error.PropertyName ?? "", error.ErrorMessage);
                }
            }
            catch (ApplicationException e)
            {
                // display error message
                ModelState.AddModelError("", e.Message);
            }

            // If we got this far, something failed, redisplay form
            return View(
                new AccountFormViewModel(
                    user, "PasswordQuestionID", "Question"
                    ));
        }

        public ActionResult Activate(int id, string key)
        {
            using (new UnitOfWorkScope(false))
            {
                var model = new UserModel();
                var user = model.FindById(id, LoadUser.PasswordQuestion);

                if (!user.Salt.Equals(key))
                {
                    return View("NoAccess");
                }

                user.Status = true;
                model.Update(user);
                return View("Activated");
            }
        }

        public ActionResult Details()
        {
            using (new UnitOfWorkScope(false))
            {
                var model = new UserModel();
                var user = model.FindByEmail(User.Identity.Name, LoadUser.PasswordQuestion).FirstOrDefault();
                return View(user);
            }
        }

        [Authorize]
        public ActionResult ChangePassword()
        {

            return View();
        }

        [Authorize]
        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "Exceptions result in password not being changed.")]
        public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword)
        {
            if (!ValidateChangePassword(currentPassword, newPassword, confirmPassword))
            {
                return View();
            }

            try
            {
                if (MembershipService.ChangePassword(User.Identity.Name, currentPassword, newPassword))
                {
                    return RedirectToAction("ChangePasswordSuccess");
                }
                ModelState.AddModelError("_FORM", "The current password is incorrect or the new password is invalid.");
                return View();
            }
            catch
            {
                ModelState.AddModelError("_FORM", "The current password is incorrect or the new password is invalid.");
                return View();
            }
        }

        public ActionResult ChangePasswordSuccess()
        {

            return View();
        }

        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (filterContext.HttpContext.User.Identity is WindowsIdentity)
            {
                throw new InvalidOperationException("Windows authentication is not supported.");
            }
        }

        #region Validation Methods

        private bool ValidateChangePassword(string currentPassword, string newPassword, string confirmPassword)
        {
            if (String.IsNullOrEmpty(currentPassword))
            {
                ModelState.AddModelError("currentPassword", "You must specify a current password.");
            }
            if (newPassword == null || newPassword.Length < Models.Dtos.User.MinRequiredPasswordLength)
            {
                ModelState.AddModelError("newPassword",
                    String.Format(CultureInfo.CurrentCulture,
                         "You must specify a new password of {0} or more characters.",
                         Models.Dtos.User.MinRequiredPasswordLength));
            }

            if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal))
            {
                ModelState.AddModelError("_FORM", "The new password and confirmation password do not match.");
            }

            return ModelState.IsValid;
        }

        private bool ValidateLogOn(string userName, string password)
        {
            if (String.IsNullOrEmpty(userName))
            {
                ModelState.AddModelError("username", "You must specify a username.");
            }
            if (String.IsNullOrEmpty(password))
            {
                ModelState.AddModelError("password", "You must specify a password.");
            }
            try
            {
                if (!MembershipService.ValidateUser(userName, password))
                {
                    ModelState.AddModelError("_FORM", "The username or password provided is incorrect.");
                }
            }
            catch (ApplicationException e)
            {
                ModelState.AddModelError("_FORM", e.Message);
            }

            return ModelState.IsValid;
        }

        private void ValidateRegistration(
            User user,
            string confirmPassword
            )
        {
            if (String.IsNullOrEmpty(confirmPassword))
            {
                ModelState.AddModelError("confirmPassword", "Field Required");
            }

            if (!String.Equals(user.Password, confirmPassword, StringComparison.Ordinal))
            {
                ModelState.AddModelError("_FORM", "The password and confirmation password do not match.");
            }

            if (!ModelState.IsValid)
                throw new ApplicationException("Invalid confirmation password.");
        }

        #endregion
    }

    // The FormsAuthentication type is sealed and contains static members, so it is difficult to
    // unit test code that calls its members. The interface and helper class below demonstrate
    // how to create an abstract wrapper around such a type in order to make the AccountController
    // code unit testable.

    public interface IFormsAuthentication
    {
        void SignIn(string userName, bool createPersistentCookie);
        void SignOut();
    }

    public class FormsAuthenticationService : IFormsAuthentication
    {
        public void SignIn(string userName, bool createPersistentCookie)
        {
            //FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
            string roles = userName.Equals(ConfigHelper.GetConfigValue("Admin")) ? "Admin,Member" : "Member";

            FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
              1,
              userName,
              DateTime.Now,
              DateTime.Now.AddMinutes(20),  // expiry 
              createPersistentCookie,  // remember 
              roles,
              "/");

            HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authTicket));
            HttpContext.Current.Response.Cookies.Add(cookie);
        }
        public void SignOut()
        {
            FormsAuthentication.SignOut();
        }
    }

    public interface IMembershipService
    {
        bool ValidateUser(string userName, string password);
        void CreateUser(
            User user,
            int passwordQuestionId
            );
        bool ChangePassword(string userName, string oldPassword, string newPassword);
    }

    public class AccountMembershipService : IMembershipService
    {
        private UserModel _model;
        private readonly int _saltSize = ConfigHelper.GetConfigValueAsInt("SaltSize");


        public bool ValidateUser(string userName, string password)
        {
            using (new UnitOfWorkScope(false))
            {
                _model = new UserModel();
                return _model.Validate(userName, password);
            }
        }

        public void CreateUser(
            User user,
            int passwordQuestionId
            )
        {
            using (new UnitOfWorkScope(false))
            {
                _model = new UserModel();
                var pqModel = new PasswordQuestionModel();
                var question = pqModel.FindById(passwordQuestionId);
                user.PasswordQuestion = question;

                _model.Create(user, _saltSize);
            }
        }

        public bool ChangePassword(string userName, string oldPassword, string newPassword)
        {
            try
            {
                using (new UnitOfWorkScope(false))
                {
                    _model = new UserModel();
                    var currentUser = _model.FindByEmail(userName, LoadUser.PasswordQuestion).SingleOrDefault();
                    return _model.ChangePassword(currentUser, oldPassword, newPassword);
                }
            }
            catch (ApplicationException)
            {
                return false;
            }
        }
    }
}
